package model.compresion.LZP;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;

import model.archivo.ArchivoBloques;
import model.registro.ConversionesTiposDatos;


public class TablaLZP {
	
	private ArrayList<RegistroLZP> tabla = new ArrayList<RegistroLZP>();
	private ArchivoBloques persistenciaTabla;
	private int cantBloques;
	private int cantRegsNoPersis; 
	public static final int registrosPorBloque = 292;
	public static final int tamanioRegistro = 14;
	
	public TablaLZP() throws FileNotFoundException, IOException{
		cantBloques = 0;
		cantRegsNoPersis = 0;
	}
	//validar caso en el que se recibe un caracter eof antes del fin del array y caso en que no hay eof
	public byte[][] comprimir(byte[] bytesAComprimir) throws IOException {
		persistenciaTabla = new ArchivoBloques("persistenciaTablaLZP.dat", 4096, true);
		cantBloques = 0;
		int posTabla=0;
		byte[] bytesAgregar = new byte[2];
		byte[] salida = new byte[6];
		int cantidadEleActuales=0;
		for (int pos = 0; ((pos < bytesAComprimir.length)&&(posTabla<bytesAComprimir.length)); pos+=2) {
			//System.out.print("posicion: "+(posTabla/2)+": ");
			bytesAgregar[0] = bytesAComprimir[posTabla];
			bytesAgregar[1] = bytesAComprimir[posTabla+1];
			//System.out.println(codificador.toShort(bytesAgregar));
			RegistroLZP registroLZP = new RegistroLZP();
			
			registroLZP.setPosicion(posTabla/2);
						
			if(pos<=3){
				registroLZP.setContexto(null);
				if (pos==0){
					//coloco longitud en cero, caracter de contexto en vacio y caracter actual 
					salida[0]= ConversionesTiposDatos.ShortToBytes((short)0)[0];
					salida[1]= ConversionesTiposDatos.ShortToBytes((short)0)[1];
					salida[2]= ConversionesTiposDatos.ShortToBytes((short)65535)[0];
					salida[3]= ConversionesTiposDatos.ShortToBytes((short)65535)[1];
					salida[4]= bytesAComprimir[pos];
					salida[5]= bytesAComprimir[pos+1];
				} else {
					//coloco longitud en cero y seteo caracteres 
					salida[0]= ConversionesTiposDatos.ShortToBytes((short)0)[0];
					salida[1]= ConversionesTiposDatos.ShortToBytes((short)0)[1];
					salida[2]= bytesAComprimir[pos-2];
					salida[3]= bytesAComprimir[pos-1];
					salida[4]= bytesAComprimir[pos];
					salida[5]= bytesAComprimir[pos+1];
				}	
				registroLZP.setSalida((byte[])salida.clone());
				
			} else {
				byte[] contexto = new byte[4];
				
				contexto[0] = this.tabla.get(((pos-(cantBloques*registrosPorBloque*2))/2)-1).getSalida()[2];
				contexto[1] = this.tabla.get(((pos-(cantBloques*registrosPorBloque*2))/2)-1).getSalida()[3];
				contexto[2] = this.tabla.get(((pos-(cantBloques*registrosPorBloque*2))/2)-1).getSalida()[4];
				contexto[3] = this.tabla.get(((pos-(cantBloques*registrosPorBloque*2))/2)-1).getSalida()[5];
				
				registroLZP.setContexto(contexto);
				
				RegistroLZP regCompara = null;
				int indiceInferior = 0;
				int indiceSuperior = posTabla;
				boolean encontrado = false;
				short longitud = 0;
				
				//busco si hay contexto para comparar
				int cantBloquesSinLeer = cantBloques;
				boolean cargaTabla = false;
				
				for (int i = this.tabla.size()-1; (i>=0)&&(!encontrado); i--){
					regCompara = this.tabla.get(i);
				
					if (regCompara.compararContexto(contexto)){
						encontrado = true;
						indiceInferior = (regCompara.getPosicion()*2);
						longitud = 0;
						//obtengo la cantidad de aciertos
						while ((indiceSuperior<bytesAComprimir.length)&&
						(bytesAComprimir[indiceInferior]==bytesAComprimir[indiceSuperior])&&
						(bytesAComprimir[indiceInferior+1]==bytesAComprimir[indiceSuperior+1])){
							indiceInferior += 2;
							indiceSuperior += 2;
							longitud++;
						}				
					} 
					if ((i==0)&&(cantBloquesSinLeer>0)){
						if (!cargaTabla){
							cantidadEleActuales = this.tabla.size();
							cargaTabla = true;	
							persistenciaTabla.EscribirBloque(this.toBytes(), cantBloques);
						}
						obtenerBloque(cantBloquesSinLeer-1,registrosPorBloque);
						cantBloquesSinLeer--;
						i = registrosPorBloque-1;
					}
				}
				if (cargaTabla){
					obtenerBloque(cantBloques, cantidadEleActuales);	
				}
				
				salida[0] = ConversionesTiposDatos.ShortToBytes(longitud)[0];
				salida[1] = ConversionesTiposDatos.ShortToBytes(longitud)[1];
				salida[2] = bytesAComprimir[indiceSuperior-2];
				salida[3] = bytesAComprimir[indiceSuperior-1];
				if((indiceSuperior<bytesAComprimir.length)){
					salida[4] = bytesAComprimir[indiceSuperior];
					salida[5] = bytesAComprimir[indiceSuperior+1];	
				} else {
					salida[4] = ConversionesTiposDatos.ShortToBytes((short)0)[0];
					salida[5] = ConversionesTiposDatos.ShortToBytes((short)0)[0];
				}
				
				//292*14=4088 este es el tamanio maximo que se puede aocupar en 
				//un bloque sin superar su capacidad
				if (tabla.size() >= registrosPorBloque){
					//System.out.println(this.tabla.toString());
					persistenciaTabla.EscribirBloque(this.toBytes(), cantBloques);
					cantBloques++;
					this.tabla.clear();
				}
				registroLZP.setSalida((byte[])salida.clone());
				
				posTabla = indiceSuperior;
				
				
			}
			posTabla +=2;
			this.tabla.add(registroLZP);
		}

		persistenciaTabla.EscribirBloque(this.toBytes(), cantBloques);
		cantBloques++;
		cantRegsNoPersis = this.tabla.size();
		//System.out.println(this.toString());
		byte[][] salidaFinal = new byte[2][];
		salidaFinal[0] = new byte[2*(((cantBloques-1)*registrosPorBloque)+(cantRegsNoPersis))];//long
		salidaFinal[1] = new byte[2*(((cantBloques-1)*registrosPorBloque)+(cantRegsNoPersis))];//car
		this.obtenerSalida(salidaFinal[1], salidaFinal[0]);
		
//		for (int i = 0; i < salidaFinal[0].length; i+=2) {
//			System.out.print(/*i +"-car: "+*/ConversionesTiposDatos.BytesToShort(Arrays.copyOfRange(salidaFinal[1], i, i+2)));
//			System.out.print(/*"-long: "+*/ConversionesTiposDatos.BytesToShort(Arrays.copyOfRange(salidaFinal[0], i, i+2)));
//		}
//		System.out.println();
//		persistenciaTabla.cerrarArchivo();
		//this.descomprimirSinPersis(salidaCar, salidaLong, bytesAComprimir);
//		this.descomprimir(salidaFinal[1], salidaFinal[0], bytesAComprimir);
		
		return salidaFinal;
	}
	
	
	private void obtenerBloque(int bloque, int bloquesALeer) throws IOException {
		byte[] bytesALeer = new byte[4096];
		persistenciaTabla.LeerBloque(bytesALeer, bloque);
		this.toTabla(bytesALeer, bloquesALeer);	
	}
	
	private void toTabla(byte[] bytesALeer, int cantALeer) {
		ArrayList<RegistroLZP> tablaAux = new ArrayList<RegistroLZP>();
		for (int i = 0; i < cantALeer; i++) {
			RegistroLZP registroAgregar = new RegistroLZP();
			registroAgregar.toRegister(Arrays.copyOfRange(bytesALeer, (i*tamanioRegistro), ((i+1)*tamanioRegistro)));			
			tablaAux.add(registroAgregar);
		}
		this.tabla = tablaAux;
	}
	
	//El archivo debe estar abierto
	public void obtenerSalida(byte[] salidaCar, byte[] salidaLong) throws IOException{
		byte[] bytes = new byte[4096];
		for(int i = 0; i<cantBloques; i++){
			this.tabla.clear();
  			persistenciaTabla.LeerBloque(bytes, i);
			if (i<cantBloques-1)
				this.toTabla(bytes, registrosPorBloque);
			else
				this.toTabla(bytes, cantRegsNoPersis);
			//System.out.println(this.toString());
			for (int j = 0; j < (this.tabla.size()*2); j+=2) {
				salidaLong[j+(2*i*registrosPorBloque)]=this.tabla.get(j/2).getSalida()[0];
				salidaLong[j+(2*i*registrosPorBloque)+1]=this.tabla.get(j/2).getSalida()[1];
				salidaCar[j+(2*i*registrosPorBloque)]=this.tabla.get(j/2).getSalida()[4];
				salidaCar[j+(2*i*registrosPorBloque)+1]=this.tabla.get(j/2).getSalida()[5];
			}
		}
	}
	
	public byte[] descomprimir(byte[] salidaCar, byte[] salidaLong/*, byte[] bytesAComprimir*/) throws IOException{
		persistenciaTabla = new ArchivoBloques("persistenciaTablaLZP.dat", 4096, true);
		cantBloques = 0;
		this.tabla.clear();
		ArrayList<Byte> bytes = new ArrayList<Byte>();
		int posBytesDescomprimir = 0;
		for (int pos = 0; pos < salidaCar.length; pos+=2) {
			RegistroLZP registroLZP = new RegistroLZP();
			if(pos<3){
				registroLZP.setContexto(null);
				registroLZP.setPosicion(posBytesDescomprimir/2);
				bytes.add(salidaCar[pos]);
				bytes.add(salidaCar[pos+1]);
			} else {
				byte[] contexto = new byte[4];
				contexto[0] = bytes.get(posBytesDescomprimir-4);
				contexto[1] = bytes.get(posBytesDescomprimir-3);
				contexto[2] = bytes.get(posBytesDescomprimir-2);
				contexto[3] = bytes.get(posBytesDescomprimir-1);
				registroLZP.setContexto(contexto);
				registroLZP.setPosicion(posBytesDescomprimir/2);
				short longitud = ConversionesTiposDatos.BytesToShort(Arrays.copyOfRange(salidaLong, pos, pos+2));
				if(longitud==0){
					bytes.add(salidaCar[pos]);
					bytes.add(salidaCar[pos+1]);					
				} else {
					RegistroLZP regCompara = null;
					boolean encontrado = false;
					int indiceInferior = 0;
					//busco si hay contexto para comparar
					int cantBloquesSinLeer = cantBloques;
					boolean cargaTabla = false;
					for (int i = this.tabla.size()-1; (i>=0)&&(!encontrado); i--){
						regCompara = this.tabla.get(i);
					
						if (regCompara.compararContexto(contexto)){
							encontrado = true;
							indiceInferior = (regCompara.getPosicion()*2);
							//obtengo la cantidad de aciertos
							while (longitud>0){
								bytes.add(bytes.get(indiceInferior));
								bytes.add(bytes.get(indiceInferior+1));
								indiceInferior += 2;
								posBytesDescomprimir+=2;
								longitud--;
								
							}
							if ((salidaCar.length>pos))
								if (!(ConversionesTiposDatos.BytesToShort(Arrays.copyOfRange(salidaCar, pos, pos+2))==(short)0)){
									bytes.add(salidaCar[pos]);
									bytes.add(salidaCar[pos+1]);	
								}
							
						} 
						if ((i==0)&&(cantBloquesSinLeer>0)){
							if (!cargaTabla){
								cantRegsNoPersis = this.tabla.size();
								cargaTabla = true;
								persistenciaTabla.EscribirBloque(this.toBytes(), cantBloques);
							}
							obtenerBloque(cantBloquesSinLeer-1,registrosPorBloque);
							cantBloquesSinLeer--;
							i = registrosPorBloque-1;
						}
					}
					if (cargaTabla){
						obtenerBloque(cantBloques, cantRegsNoPersis);	
					}
				}
				
				if (tabla.size() >= registrosPorBloque){
					//System.out.println(this.toString());
					persistenciaTabla.EscribirBloque(this.toBytes(), cantBloques);
					cantBloques++;
					this.tabla.clear();
				}
				
			}
			this.tabla.add(registroLZP);
			
			/*if  (!bytes.get(posBytesDescomprimir).equals(bytesAComprimir[posBytesDescomprimir])){
				int i=0;
				i--;
				
			}*/
			posBytesDescomprimir +=2;
		}
		//System.out.println(this.toString());
		persistenciaTabla.cerrarArchivo();
		byte[] descompresion = new byte[bytes.size()];
		for (int i = 0; i < descompresion.length; i++) {
			descompresion[i]=bytes.get(i);
		}
		
		return descompresion;
		
		/*byte[] aux = new byte[2];
		int iteracion=0;
		
		for (Iterator iter = bytes.iterator(); iter.hasNext();) {
			if (cantidadAntesdePers==iteracion)
				System.out.print("Persistio");
			aux[0] = (Byte) iter.next();
			aux[1] = (Byte) iter.next();
			System.out.print(iteracion/2+"- "+ ConversionesTiposDatos.BytesToShort(aux)+" ");
			System.out.println(ConversionesTiposDatos.BytesToShort(Arrays.copyOfRange(bytesAComprimir, iteracion,iteracion+2)));
			iteracion+=2;
		}
		System.out.println();*/
	}
	
	public void comprimirSinPersistencia(byte[] bytesAComprimir) throws IOException {
		int posTabla=0;
		byte[] bytesAgregar = new byte[2];
		byte[] salida = new byte[6];
		for (int pos = 0; ((pos < bytesAComprimir.length)&&(posTabla<bytesAComprimir.length)); pos+=2) {
			bytesAgregar[0] = bytesAComprimir[posTabla];
			bytesAgregar[1] = bytesAComprimir[posTabla+1];
			RegistroLZP registroLZP = new RegistroLZP();
			
			registroLZP.setPosicion(posTabla/2);
						
			if(pos<=3){
				registroLZP.setContexto(null);
				byte[] caracter = Arrays.copyOfRange(bytesAComprimir, pos, pos+2);
				if (!(ConversionesTiposDatos.BytesToShort(caracter)==65535)){
					if (pos==0){
						//coloco longitud en cero, caracter de contexto en vacio y caracter actual 
						salida[0]= ConversionesTiposDatos.ShortToBytes((short)0)[0];
						salida[1]= ConversionesTiposDatos.ShortToBytes((short)0)[1];
						salida[2]= ConversionesTiposDatos.ShortToBytes((short)65535)[0];
						salida[3]= ConversionesTiposDatos.ShortToBytes((short)65535)[1];
						salida[4]= bytesAComprimir[pos];
						salida[5]= bytesAComprimir[pos+1];
					} else {
						//coloco longitud en cero y seteo caracteres 
						salida[0]= ConversionesTiposDatos.ShortToBytes((short)0)[0];
						salida[1]= ConversionesTiposDatos.ShortToBytes((short)0)[1];
						salida[2]= bytesAComprimir[pos-2];
						salida[3]= bytesAComprimir[pos-1];
						salida[4]= bytesAComprimir[pos];
						salida[5]= bytesAComprimir[pos+1];
					}	
					registroLZP.setSalida((byte[])salida.clone());
				}
				
			} else {
				byte[] contexto = new byte[4];
				
				contexto[0] = this.tabla.get(((pos-(cantBloques*registrosPorBloque*2))/2)-1).getSalida()[2];
				contexto[1] = this.tabla.get(((pos-(cantBloques*registrosPorBloque*2))/2)-1).getSalida()[3];
				contexto[2] = this.tabla.get(((pos-(cantBloques*registrosPorBloque*2))/2)-1).getSalida()[4];
				contexto[3] = this.tabla.get(((pos-(cantBloques*registrosPorBloque*2))/2)-1).getSalida()[5];
				
				registroLZP.setContexto(contexto);
				
				RegistroLZP regCompara = null;
				int indiceInferior = 0;
				int indiceSuperior = posTabla;
				boolean encontrado = false;
				short longitud = 0;
				
				//busco si hay contexto para comparar
				
				for (int i = this.tabla.size()-1; (i>=0)&&(!encontrado); i--){
					regCompara = this.tabla.get(i);
				
					if (regCompara.compararContexto(contexto)){
						encontrado = true;
						indiceInferior = (regCompara.getPosicion()*2);
						longitud = 0;
						//obtengo la cantidad de aciertos
						while ((indiceSuperior<bytesAComprimir.length)&&(bytesAComprimir[indiceInferior]==bytesAComprimir[indiceSuperior])&&
							  (bytesAComprimir[indiceInferior+1]==bytesAComprimir[indiceSuperior+1])){
							indiceInferior += 2;
							indiceSuperior += 2;
							longitud++;
						}				
					} 
					
				}
				
				salida[0] = ConversionesTiposDatos.ShortToBytes(longitud)[0];
				salida[1] = ConversionesTiposDatos.ShortToBytes(longitud)[1];
				salida[2] = bytesAComprimir[indiceSuperior-2];
				salida[3] = bytesAComprimir[indiceSuperior-1];
				if((indiceSuperior<bytesAComprimir.length)){
					salida[4] = bytesAComprimir[indiceSuperior];
					salida[5] = bytesAComprimir[indiceSuperior+1];	
				} else {
					salida[4] = ConversionesTiposDatos.ShortToBytes((short)0)[0];
					salida[5] = ConversionesTiposDatos.ShortToBytes((short)0)[0];
				}

				registroLZP.setSalida((byte[])salida.clone());
				
				posTabla = indiceSuperior;
				
				byte[] caracter = Arrays.copyOfRange(salida, 0, 2);
				if (ConversionesTiposDatos.BytesToShort(caracter)==65535){
					break;
				}
			}
			posTabla +=2;
			this.tabla.add(registroLZP);
		}
		byte salidaLong[] = new byte[this.tabla.size()*2];
		byte salidaCar[] = new byte[this.tabla.size()*2];
		for (int j = 0; j < (this.tabla.size()*2); j+=2) {
			salidaLong[j]=this.tabla.get(j/2).getSalida()[0];
			salidaLong[j+1]=this.tabla.get(j/2).getSalida()[1];
			salidaCar[j]=this.tabla.get(j/2).getSalida()[4];
			salidaCar[j+1]=this.tabla.get(j/2).getSalida()[5];
		}
		//System.out.println(this.toString());
		for (int i = 0; i < salidaCar.length; i+=2) {
			System.out.print(/*i +"-car: "+*/ConversionesTiposDatos.BytesToShort(Arrays.copyOfRange(salidaCar, i, i+2)));
			System.out.print(/*"-long: "+*/ConversionesTiposDatos.BytesToShort(Arrays.copyOfRange(salidaLong, i, i+2)));
		}
		System.out.println();
		//this.descomprimirSinPersis(salidaCar, salidaLong, bytesAComprimir);
		//this.descomprimir(salidaCar, salidaLong, bytesAComprimir);

	}
	
	public void descomprimirSinPersis(byte[] salidaCar, byte[] salidaLong, byte[] bytesAComprimir) throws IOException{
		int cantidadAntesdePers = 0;
		this.tabla.clear();
		ArrayList<Byte> bytes = new ArrayList<Byte>();
		int posBytesDescomprimir = 0;
		for (int pos = 0; pos < salidaCar.length; pos+=2) {
			RegistroLZP registroLZP = new RegistroLZP();
			if(pos<3){
				registroLZP.setContexto(null);
				registroLZP.setPosicion(posBytesDescomprimir/2);
				bytes.add(salidaCar[pos]);
				bytes.add(salidaCar[pos+1]);
			} else {
				byte[] contexto = new byte[4];
				contexto[0] = bytes.get(posBytesDescomprimir-4);
				contexto[1] = bytes.get(posBytesDescomprimir-3);
				contexto[2] = bytes.get(posBytesDescomprimir-2);
				contexto[3] = bytes.get(posBytesDescomprimir-1);
				registroLZP.setContexto(contexto);
				registroLZP.setPosicion(posBytesDescomprimir/2);
				short longitud = ConversionesTiposDatos.BytesToShort(Arrays.copyOfRange(salidaLong, pos, pos+2));
				if(longitud==0){
					bytes.add(salidaCar[pos]);
					bytes.add(salidaCar[pos+1]);					
				} else {
					RegistroLZP regCompara = null;
					boolean encontrado = false;
					int indiceInferior = 0;
					//busco si hay contexto para comparar
					for (int i = this.tabla.size()-1; (i>=0)&&(!encontrado); i--){
						regCompara = this.tabla.get(i);
					
						if (regCompara.compararContexto(contexto)){
							encontrado = true;
							indiceInferior = (regCompara.getPosicion()*2);
							//obtengo la cantidad de aciertos
							while (longitud>0){
								bytes.add(bytes.get(indiceInferior));
								bytes.add(bytes.get(indiceInferior+1));
								indiceInferior += 2;
								posBytesDescomprimir+=2;
								longitud--;
								if (longitud==0){
									String g = "";
									g+="";
								}
							}
							if (salidaCar.length>pos){
								bytes.add(salidaCar[pos]);
								bytes.add(salidaCar[pos+1]);	
							}
							
						} 
						
					}
					
				}
				
			}
			this.tabla.add(registroLZP);
			
			/*if  (!bytes.get(posBytesDescomprimir).equals(bytesAComprimir[posBytesDescomprimir])){
				int i=0;
				i--;
				
			}*/
			posBytesDescomprimir +=2;
		}
		byte[] aux = new byte[2];
		int iteracion=0;
		System.out.println(this.toString());
		for (Iterator iter = bytes.iterator(); iter.hasNext();) {
			if (cantidadAntesdePers==iteracion)
				System.out.print("Persistio");
			aux[0] = (Byte) iter.next();
			aux[1] = (Byte) iter.next();
			System.out.print(iteracion/2+"- "+ ConversionesTiposDatos.BytesToShort(aux)+" ");
			System.out.println(ConversionesTiposDatos.BytesToShort(Arrays.copyOfRange(bytesAComprimir, iteracion,iteracion+2)));
			iteracion+=2;
		}
		System.out.println();
	}
	public String toString(){
		String string="";
		for (int i = 0; i < this.tabla.size(); i++) {
			string += tabla.get(i).toString();
		}
		string += "Tamaño: " + tabla.size();
		return string;
	}
	
	public byte[] toBytes(){
		byte[] byteArray = new byte[this.tabla.size()*tamanioRegistro];
		for (int posTabla = 0; posTabla < tabla.size(); posTabla++) {
			for (int posRegistro = 0; posRegistro < tamanioRegistro; posRegistro++) {
				byteArray[(posTabla*tamanioRegistro)+posRegistro] = tabla.get(posTabla).toBytes()[posRegistro];
			}
		}
		return byteArray;
	}
}
